package gui.view.standard;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.KeyEventPostProcessor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

import model.MetroModel;


import gui.view.ActiveMetroView;
import gui.view.State;
import gui.view.drawers.editor.EditorsDrawer;
import model.objects.EdiStation;
import model.objects.EdiTunnel;
import model.readers.PMetro.ShiftModel;

import com.nightpiter.controller.log.Logger;
import com.nightpiter.model.objects.Line;

import com.nightpiter.model.objects.Coordinates;
import com.nightpiter.model.objects.Station;
import com.nightpiter.model.objects.Transfer;
import com.nightpiter.model.objects.Transition;
import com.nightpiter.model.objects.Tunnel;


/**
 * different triggers StandartView for listeners
 * @author m06sia
 *
 */



public class StandardView extends ActiveMetroView {
	private MetroModel model;
	private EditorsDrawer drawer;
	private List<EdiStation> stationSelection = new LinkedList<EdiStation>();
	private List<EdiTunnel> tunnelSelection = new LinkedList<EdiTunnel>();
	private List<gui.properties.SelectionListener> selectionListeners = new LinkedList<gui.properties.SelectionListener>(); 
	private Point panelCenter = new Point(150,200);
	private final JPanel panel = this;
	private final ViewTriggers triggers = new ViewTriggers();
	private float minScale;
	public ViewTriggers getTriggers() {
		return triggers;
	}


	public StandardView(MetroModel model, final EditorsDrawer drawer, JFrame parent) {
		this.model = model;
		this.drawer = drawer;
		this.setPreferredSize(new Dimension(500,500));
		this.addMouseMotionListener(new MapScrollListener());		
		this.addMouseListener(new AuxiliaryListener());
		this.addMouseListener(new AddStationListener());
		this.addMouseListener(new AddTunnelListener());
		this.addMouseMotionListener(new StationMoveListener());
		this.addMouseMotionListener(new LabelMoveListener());
		this.addMouseWheelListener(new MapScaleListener());
		
		this.addMouseListener(new StationsSelectionListener());
		this.addMouseListener(new TunnelSelectionListener());
		this.addMouseListener(new AddTransferListener());
		
		javax.swing.FocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(new KeyEventPostProcessor(){

			@Override
			public boolean postProcessKeyEvent(KeyEvent e) {
				
				if(JTextField.class.isInstance(e.getSource())) {return false;}
				if(e.getID()!=e.KEY_PRESSED) return false;
				if(e.getKeyCode()==e.VK_DELETE) {

					for(Station s: stationSelection) {
						Logger.log(s.getName());
					}
					
					MetroModel.getInstance().remove(stationSelection, tunnelSelection);

					stationSelection = new LinkedList<EdiStation>();
					tunnelSelection = new LinkedList<EdiTunnel>();
					selectionChanged();
				}
				return false;
			}
			
		});
		
	}
	
	
	@Override
	public void paintComponent(Graphics g) {				
		super.paintComponent(g);		
		drawer.setDrawerCenter(this.getWidth()/2, this.getHeight()/2);
		drawer.draw(stationSelection, tunnelSelection,g, triggers.showStationsLabels);
	}
	
	private class StationMoveListener implements  MouseMotionListener{

		//TODO local to the absolute coordinates transform
		@Override
		public void mouseDragged(MouseEvent arg0) {
			
			 
			if(triggers.state!=State.STATIONS_MOVE ) return;
			
			
			int dx = arg0.getPoint().x - triggers.lastDragPoint.x;
			int dy = arg0.getPoint().y - triggers.lastDragPoint.y;
			
			moveSelectedStations(dx/drawer.getScale(),dy/drawer.getScale());			
			triggers.lastDragPoint = new Point(arg0.getPoint().x,arg0.getPoint().y);
			
			repaint();
			//repaintStationsAndTheirTransitions(selection);
			
		}

		@Override
		public void mouseMoved(MouseEvent arg0) {
			
			
		}
		
		void moveSelectedStations(float dx, float dy) {
			
			model.moveStations(stationSelection,dx,dy);
		
	}	
		
	}
	
	private class LabelMoveListener implements  MouseMotionListener{

		//TODO local to the absolute coordinates transform
		@Override
		public void mouseDragged(MouseEvent arg0) {
			
			if(triggers.state!=State.MOVE_LABEL) return;
			
			int dx = arg0.getPoint().x -triggers.lastDragPoint.x; 
			int dy = arg0.getPoint().y -triggers.lastDragPoint.y;
			EdiStation station = triggers.draggedLabelStation;
			if(station!=null) {
				Point oldTextPosition = station.getTextRelativePosition();
				
				MetroModel.getInstance().moveStationLabel(station, oldTextPosition.x+dx,oldTextPosition.y+dy);
				
			}
			triggers.lastDragPoint = new Point(arg0.getPoint().x,arg0.getPoint().y);
			
			
			repaint();
			//repaintStationsAndTheirTransitions(selection);
			
		}

		@Override
		public void mouseMoved(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}
		
	}
	
	private class MapScrollListener implements MouseMotionListener {
		public void mouseDragged(MouseEvent arg0) {
			
			//Logger.log("" + arg0.BUTTON2_MASK);
			if(triggers.state!=State.SCROLL && arg0.getModifiers()!=MouseEvent.BUTTON2_MASK) return;
			
			int dx = arg0.getPoint().x -triggers.lastDragPoint.x; 
			int dy = arg0.getPoint().y -triggers.lastDragPoint.y;
			drawer.shift(dx, dy);
			triggers.lastDragPoint = new Point(arg0.getPoint().x,arg0.getPoint().y);
			
			panel.repaint();
		}

		public void mouseMoved(MouseEvent arg0) {
			
			
		}

	}
	
	private class AuxiliaryListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			triggers.lastDragPoint = new Point(arg0.getPoint().x, arg0.getPoint().y);
			if(triggers.state==State.MOVE_LABEL) 
				triggers.draggedLabelStation= drawer.getLabelsStation(arg0.getPoint());
			
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			triggers.lastDragPoint = null;
			
		}
	}
	
	private class AddStationListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {
			if(arg0.getClickCount()==2) {
				MetroModel.getInstance().addStation("", drawer.getAbsolutePoint(new Point(arg0.getPoint().x,arg0.getPoint().y)), null, null, null);
			}
			
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			
			
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			
			
		}
	}
	private class AddTunnelListener implements MouseListener {
		
		private EdiStation from=null;
		@Override
		public void mouseClicked(MouseEvent arg0) {
			if(triggers.state==State.ADD_TRANSITION) {
				if(from==null) {
					
					from = (!drawer.getStation(arg0.getPoint()).isEmpty()) ? drawer.getStation(arg0.getPoint()).get(0) : null;
					if(from!=null) System.out.println("from "+from.getName());
					return;
				}
				EdiStation to = (!drawer.getStation(arg0.getPoint()).isEmpty()) ? drawer.getStation(arg0.getPoint()).get(0) : null;
				if(from==to) return;
				if(to==null) {
					from=null;
					return;
				}				
				if(!isDoubling(from,to, triggers.addTunelsLine))
					MetroModel.getInstance().addTunnel(from, to, triggers.addTunelsLine,00,false);				
				from=to;
			}
		}
		
		private boolean isDoubling(Station from, Station to, Line line) {
			for (Tunnel t:from.getTunnels()) {
				Logger.log(""+t+"\n:"+from+";;"+to+":");
				if(t.getToStation()==to && t.getLine() == line) return true;
			}
			return false;
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}
		
	}
	
private class AddTransferListener implements MouseListener {
		
		private EdiStation from=null;
		@Override
		public void mouseClicked(MouseEvent arg0) {
			if(triggers.state==State.ADD_TRANSFER) {
				if(from==null) {
					
					from = (!drawer.getStation(arg0.getPoint()).isEmpty()) ? drawer.getStation(arg0.getPoint()).get(0) : null;
					if(from!=null) System.out.println("from "+from.getName());
					return;
				}
				EdiStation to = (!drawer.getStation(arg0.getPoint()).isEmpty()) ? drawer.getStation(arg0.getPoint()).get(0) : null;
				if(from==to) return;
				if(to==null) {
					from=null;
					return;
				}
				
				if(!isDoubling(from,to))
					MetroModel.getInstance().addTransfer(from, to, 00);				
				from=to;
			}
		}

		private boolean isDoubling(Station from, Station to) {
			for (Transfer t:from.getTransfers()) {
				if(t.getToStation()==to) return true;
			}
			return false;
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}
		
	}

	private class MapScaleListener implements MouseWheelListener {
		private final float SCALE_STEP = 1.1f;
		
		@Override
		public void mouseWheelMoved(MouseWheelEvent arg0) {
			float scaleShift = (arg0.getWheelRotation()<0) ? SCALE_STEP : 1/SCALE_STEP;
			float currScale = drawer.getScale();
			if(currScale*scaleShift>minScale) drawer.setScale(currScale*scaleShift);
			repaint();
			
		}
		
	}
	private class TunnelSelectionListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {			
			if(!ViewTriggers.tunnelSelectingEnabled) return;
			if(arg0.isShiftDown()) {
				for(EdiTunnel tunnel:drawer.getTunnel(arg0.getPoint())) {
					if(tunnelSelection.contains(tunnel)) tunnelSelection.remove(tunnel);
					else tunnelSelection.add(tunnel);
				}
				
			} else {
				tunnelSelection = drawer.getTunnel(arg0.getPoint());
			}
			if(stationSelection!=null) {
				/*repaintOnlyStations(oldSelection);
				repaintOnlyStations(stationSelection);*/
				repaintTunnels(tunnelSelection);
				for(Tunnel tunnel: tunnelSelection) {
					System.out.println(tunnel.toString());
				}
			}
			selectionChanged();
			
		}

		private void repaintTunnels(List<EdiTunnel> tunnelSelection) {
			//TODO
			repaint();
			
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}
		
	}
	private class StationsSelectionListener implements MouseListener {

		private boolean processClick;
		@Override
		public void mouseClicked(MouseEvent arg0) {
			if(!processClick) return;
			List<EdiStation> pointStation = drawer.getStation(arg0.getPoint());
			if(arg0.isShiftDown()) {
				for(EdiStation station: pointStation) {
					if(stationSelection.contains(station)) stationSelection.remove(station);
					else stationSelection.add(station);
				}				
			} else {
				stationSelection = drawer.getStation(arg0.getPoint());
			}
			selectionChanged();
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			processClick = true;
			if(!ViewTriggers.stationSelectingEnabled) return;
			List<EdiStation> oldSelection = stationSelection;
			List<EdiStation> pointStation = drawer.getStation(arg0.getPoint());
			if(arg0.isShiftDown()) {
				for(EdiStation station:pointStation) {
					if(!stationSelection.contains(station)) {
						stationSelection.add(station);
						processClick=false;
					 }
					
				}				
			} else {
				if(!stationSelection.containsAll(pointStation))
					stationSelection = drawer.getStation(arg0.getPoint());
				//processClick = false;
			}
			if(stationSelection!=null) {
				repaintOnlyStations(oldSelection);
				repaintOnlyStations(stationSelection);
				for(EdiStation station :stationSelection) {
					System.out.println(station.getName());
				}
			}
			selectionChanged();
			
		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			// TODO Auto-generated method stub
			
		}
		
	}
	
	
	void repaintOnlyStations(List<EdiStation> stations) {
		for(EdiStation station: stations ) {
			repaint(drawer.getRedrawRect(station));
		}
	}
	
	void repaintStationsAndTheirTransitions(List<EdiStation> stations) {
		this.repaintOnlyStations(stations);
		for(EdiStation station: stations) {			
			for(Transition trans: this.getAllStationTransitions(station)) {
				repaint(drawer.getRedrawRect(trans));
			}
		}
	}
	
	private List<Transition> getAllStationTransitions(EdiStation station) {
		List<Transition> trans = new LinkedList<Transition>();
		for(Transition t:model.getTransfers()) {
			if(t.getFromStation()==station || t.getToStation() == station) {
				trans.add(t);
			}
		}
		for(Transition t:model.getTunnels()) {
			if(t.getFromStation()==station || t.getToStation() == station) {
				trans.add(t);
			}
		}
		return  trans;
	}
	
	


	@Override
	public void addSelectionListener(gui.properties.SelectionListener listener) {
		this.selectionListeners.add(listener);		
	}
	
	void selectionChanged() {
		for(gui.properties.SelectionListener l: this.selectionListeners) {			
			l.selectionSeted(stationSelection, tunnelSelection);
		}
	}


	@Override
	public void modelChanged(boolean newObjectsAdded) {
		this.repaint();		
	}
	
	
	@Override
	public void afterPanelInit() {
		
		try {
			drawer=drawer.getClass().newInstance();
		} catch (InstantiationException e) {			
			e.printStackTrace();
		} catch (IllegalAccessException e) {			
			e.printStackTrace();
		}
		fitMap();
		this.minScale = drawer.getScale()*0.01f;
		
	}


	private void fitMap() {
		Rectangle mapBounds = getMapBounds();
		drawer.setDrawerCenter(this.getWidth()/2, this.getHeight()/2);	
		Logger.log(mapBounds+"");
		float scaleX = (float)(this.getHeight()/mapBounds.getHeight());
		float scaleY = (float)(this.getWidth()/mapBounds.getWidth());
		float finalScale = Math.min(scaleX, scaleY);
		drawer.setScale(finalScale);
		//drawer.setDrawerCenter(0, 0);
		drawer.shift(-(int)(mapBounds.getCenterX()*finalScale), -(int)(mapBounds.getCenterY()*finalScale));
		Logger.log(mapBounds.getMinX()*finalScale+"");
	}
	
	
	
	
}
